package com.hay.pay.support.wechat;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.hay.pay.constant.PayConstant;
import com.hay.pay.exception.PayException;
import com.hay.pay.util.DateUtil;
import com.hay.pay.util.CertificateUtil;
import com.hay.pay.util.WebUtil;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.Signature;
import java.util.*;

/**
 * 微信支付签名
 * <a href="https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html"/>
 * @author LiMouRen
 * @date 2024/1/25
 */
@Slf4j
public class WechatPaySign {

    /**
     * 时间戳误差
     */
    private static final long TIMESTAMP_ERROR_GAP = 5*60*1000;

    /**
     * 构造签名串
     *
     * @param signMessage 待签名的参数
     * @return 构造后带待签名串
     */
    public static String buildSignMessage(List<String> signMessage) {
        if (signMessage == null || signMessage.size() <= 0) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        return sbf.toString();
    }


    /**
     * 生成签名
     * @param mchPrivateKey 商户证书秘钥
     * @param signDataList 待签名的参数
     * @return 构造后带待签名串
     */
    public static String sign(String mchPrivateKey,List<String> signDataList) {
        String message = buildSignMessage(signDataList);
        return sign(mchPrivateKey,message);
    }

    /**
     * 生成签名
     * @param mchPrivateKey 商户证书秘钥
     * @param signMessageStr 待签名的参数
     * @return 构造后带待签名串
     */
    public static String sign(String mchPrivateKey,String signMessageStr) {
        try{
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(CertificateUtil.loadPrivateKey(mchPrivateKey));
            sign.update(signMessageStr.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (Exception e){
            throw new PayException(e.getMessage());
        }
    }

    /**
     * 解密签名
     * @param signData 签名内容
     * @param waitVerifySign 待验证的签名签名
     * @param publicKey 公钥
     */
    public static Boolean verifySign(String signData,String waitVerifySign,String publicKey) {
        try{
            byte[] decodeWaitVerifySign = Base64.getDecoder().decode(waitVerifySign.getBytes(StandardCharsets.UTF_8));
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initVerify(CertificateUtil.loadPublicKey(publicKey));
            signature.update(signData.getBytes(StandardCharsets.UTF_8));
            return signature.verify(decodeWaitVerifySign);
        } catch (Exception e){
            throw new PayException(e.getMessage());
        }
    }

    /**
     * 解密回调
     */
    public static String decryptNotify(HttpServletRequest request,
                                       String paySerialNo,
                                       String payPublicKey,
                                       String v3Key) {
        String requestBody = WebUtil.getRequestBody(request);
        // 验证回调签名,确认请求来自微信
        verifyNotify(request,requestBody,paySerialNo,payPublicKey);
        return decryptNotify(requestBody, v3Key);
    }

    /**
     * 验证回调签名,确认请求来自微信
     */
    public static void verifyNotify(HttpServletRequest request,String requestBody,String paySerialNo, String payPublicKey) {
        // 平台证书序列号
        String notifySerial = request.getHeader(PayConstant.WECHAT_PAY_SERIAL);
        if (Objects.isNull(notifySerial) || notifySerial.length() == 0 || !notifySerial.equals(paySerialNo)){
            throw new PayException(PayConstant.WECHAT_PAY_SERIAL+"无效,非法回调请求");
        }
        // 应答时间戳,允许最多5分钟的时间偏差
        String notifyTimestampStr = request.getHeader(PayConstant.WECHAT_PAY_TIMESTAMP);
        if (Objects.isNull(notifyTimestampStr) || notifyTimestampStr.length() == 0){
            throw new PayException(PayConstant.WECHAT_PAY_TIMESTAMP+"为空,非法回调请求");
        }
        long notifyTimestamp = Long.parseLong(notifyTimestampStr);
        Date now = new Date();
        if (now.getTime() - notifyTimestamp > TIMESTAMP_ERROR_GAP){
            throw new PayException(PayConstant.WECHAT_PAY_TIMESTAMP+"误差超过5分钟,非法回调请求");
        }
        // 应答随机串
        String notifyNonce = request.getHeader(PayConstant.WECHAT_PAY_NONCE);
        // 应答签名
        String notifySignature = request.getHeader(PayConstant.WECHAT_PAY_SIGNATURE);
        // 验证应答签名
        List<String> signDataList = new ArrayList<>();
        signDataList.add(notifyTimestampStr);
        signDataList.add(notifyNonce);
        signDataList.add(requestBody);
        String signMessage = buildSignMessage(signDataList);
        boolean checkByPublicKey = verifySign(signMessage, notifySignature, payPublicKey);
        if (!checkByPublicKey){
            log.error("verifyNotify , signMessage: {},notifySignature:{},payPublicKey:{}",signMessage,notifySignature,payPublicKey);
            throw new PayException("应答签名错误,非法回调请求");
        }
    }

    /**
     * 解密v3支付异步通知
     *
     * @param body 异步通知密文
     * @param apiV3Key api v3密钥
     * @return 异步通知明文
     */
    public static String decryptNotify(String body, String apiV3Key) {
        // 获取平台证书序列号
        JSONObject resultObject = JSONObject.parseObject(body);
        JSONObject resource = resultObject.getJSONObject("resource");
        String cipherText = resource.getString("ciphertext");
        String nonceStr = resource.getString("nonce");
        String associatedData = resource.getString("associated_data");
        return aesDecrypt(apiV3Key, cipherText, nonceStr, associatedData);
    }

    /**
     * aes 解密
     */
    private static String aesDecrypt(String apiV3Key, String cipherText, String nonceStr, String associatedData) {
        AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
        try {
            // 密文解密
            return aesUtil.decryptToString(
                    associatedData.getBytes(StandardCharsets.UTF_8),
                    nonceStr.getBytes(StandardCharsets.UTF_8),
                    cipherText
            );
        }catch (Exception e){
            log.error("apiV3Key:{},cipherText:{},nonceStr:{},associatedData:{},error:{}", apiV3Key,cipherText,nonceStr,associatedData,e.getMessage(),e);
            throw new PayException(e.getMessage());
        }
    }

    /**
     * 获取支付平台证书
     * <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml">
     * */
    public static String getPayCertificate(String mchId,
                                           String mchSerialNo,
                                           String apiV3Key,
                                           String mchPrivateKey){
        //时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        //随机字符串（用UUID去掉-就行）
        String nonce = UUID.randomUUID().toString().replace("-", "");
        String body =  "";
        //拼接要签名的字符串
        List<String> singMessage = new ArrayList<>(5);
        singMessage.add("GET");
        singMessage.add(WechatApiEnum.GET_CERTIFICATES.getUrl());
        singMessage.add(String.valueOf(timestamp));
        singMessage.add(nonce);
        singMessage.add(body);
        // 生成签名
        String sign = WechatPaySign.sign(mchPrivateKey,singMessage);
        // 要放在HttpHeader中的auth信息
        String auth = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%s\",serial_no=\"%s\",signature=\"%s\"";
        String authorization = String.format(auth,mchId,nonce,timestamp,mchSerialNo,sign);
        String apiUrl = WechatDomainEnum.CHINA.getDomain()+WechatApiEnum.GET_CERTIFICATES.getUrl();
        HttpGet httpGet = new HttpGet();
        httpGet.setURI(URI.create(apiUrl));
        httpGet.addHeader("Content-type", "application/json");
        httpGet.addHeader("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");
        httpGet.addHeader("Accept", "application/json");
        httpGet.addHeader("Authorization",authorization);
        String responseBodyStr = null;
        try {
            CloseableHttpResponse response = HttpClients.createDefault().execute(httpGet);
            responseBodyStr = EntityUtils.toString(response.getEntity());
        } catch (IOException e) {
            throw new PayException(e.getMessage());
        }
        JSONObject responseBody = JSONObject.parseObject(responseBodyStr);
        JSONObject data = getLastCertificateData(responseBody);
        JSONObject resource = data.getJSONObject("encrypt_certificate");
        String cipherText = resource.getString("ciphertext");
        String nonceStr = resource.getString("nonce");
        String associatedData = resource.getString("associated_data");
        return aesDecrypt(apiV3Key,cipherText,nonceStr,associatedData);
    }

    /**
     * 如果存在多个证书,获取过期时间最迟的
     */
    private static JSONObject getLastCertificateData(JSONObject responseBody){
        JSONArray dataArray = responseBody.getJSONArray("data");
        if (dataArray.size() == 1){
            return dataArray.getJSONObject(0);
        }
        int lastExpireIndex = 0;
        Date lastExpireDate = null;
        for (int i = 0; i < dataArray.size(); i++) {
            JSONObject data = dataArray.getJSONObject(lastExpireIndex);
            String expireTime = data.getString("expire_time");
            Date expireDate = DateUtil.parse(expireTime,"yyyy-MM-dd'T'HH:mm:ss");
            if (Objects.isNull(lastExpireDate) || expireDate.getTime() > lastExpireDate.getTime()){
                lastExpireDate = expireDate;
                lastExpireIndex = i;
            }
        }
        return dataArray.getJSONObject(lastExpireIndex);
    }

}
